﻿/*
 * Author: Gabriel Reiser
 * Contributors:
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Irrlicht.Net.Core
{
    [Serializable]
    public struct Matrix4D
    {
        //internal float[,] fields;
        public float m11, m12, m13, m14,
                     m21, m22, m23, m24,
                     m31, m32, m33, m34,
                     m41, m42, m43, m44;

        private readonly static Matrix4D zero = new Matrix4D(
                        0, 0, 0, 0,
                        0, 0, 0, 0,
                        0, 0, 0, 0,
                        0, 0, 0, 0);
        private readonly static Matrix4D identity = new Matrix4D(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);

        private readonly static Matrix4D clipSpace2dToImageSpace = new Matrix4D(
                0.5f, 0, 0, 0.5f,
                0, -0.5f, 0, 0.5f,
                0, 0, 1, 0,
                0, 0, 0, 1);

        public Vector3D Scale
        {
            get
            {
                return new Vector3D(this.m11, this.m22, this.m33);
            }
            set
            {
                this.m11 = value.x;
                this.m22 = value.y;
                this.m33 = value.z;
            }
        }

        public Vector3D Translation
        {
            get
            {
                return new Vector3D(this.m14, this.m24, this.m34);
            }
            set
            {
                this.m14 = value.x;
                this.m24 = value.y;
                this.m34 = value.z;
            }
        }

        public Matrix4D Zero { get { return zero; } }
        public Matrix4D Identity { get { return identity; } }
        public Matrix4D ClipSpace2DToImageSpace { get { return clipSpace2dToImageSpace; } }
        /*public Matrix()
        {
            fields = new float[4, 4];
        }*/
        public Matrix4D(float m1_1, float m2_1, float m3_1, float m4_1,
                      float m1_2, float m2_2, float m3_2, float m4_2,
                      float m1_3, float m2_3, float m3_3, float m4_3,
                      float m1_4, float m2_4, float m3_4, float m4_4)
        {
            m11 = m1_1; m12 = m1_2; m13 = m1_3; m14 = m1_4;
            m21 = m2_1; m22 = m2_2; m23 = m2_3; m24 = m2_4;
            m31 = m3_1; m32 = m3_2; m33 = m3_3; m34 = m3_4;
            m41 = m4_1; m42 = m4_2; m43 = m4_3; m44 = m4_4;
        }
        public Matrix4D(Vector3D XAxis, Vector3D YAxis, Vector3D ZAxis)
        {
            m11 = XAxis.x; m12 = YAxis.x; m13 = ZAxis.x; m14 = 0;
            m21 = XAxis.y; m22 = YAxis.y; m23 = ZAxis.y; m24 = 0;
            m31 = XAxis.z; m32 = YAxis.z; m33 = ZAxis.z; m34 = 0;
            m41 = 0; m42 = 0; m43 = 0; m44 = 1;
        }

        
        public static Matrix4D operator * (Matrix4D val1, Matrix4D val2)
        {
            Matrix4D result = new Matrix4D();

            result.m11 = val1.m11 * val2.m11 + val1.m12 * val2.m21 + val1.m13 * val2.m31 + val1.m14 * val2.m41;
            result.m12 = val1.m11 * val2.m12 + val1.m12 * val2.m22 + val1.m13 * val2.m32 + val1.m14 * val2.m42;
            result.m13 = val1.m11 * val2.m13 + val1.m12 * val2.m23 + val1.m13 * val2.m33 + val1.m14 * val2.m43;
            result.m14 = val1.m11 * val2.m14 + val1.m12 * val2.m24 + val1.m13 * val2.m34 + val1.m14 * val2.m44;

            result.m21 = val1.m21 * val2.m11 + val1.m22 * val2.m21 + val1.m23 * val2.m31 + val1.m24 * val2.m41;
            result.m22 = val1.m21 * val2.m12 + val1.m22 * val2.m22 + val1.m23 * val2.m32 + val1.m24 * val2.m42;
            result.m23 = val1.m21 * val2.m13 + val1.m22 * val2.m23 + val1.m23 * val2.m33 + val1.m24 * val2.m43;
            result.m24 = val1.m21 * val2.m14 + val1.m22 * val2.m24 + val1.m23 * val2.m34 + val1.m24 * val2.m44;

            result.m31 = val1.m31 * val2.m11 + val1.m32 * val2.m21 + val1.m33 * val2.m31 + val1.m34 * val2.m41;
            result.m32 = val1.m31 * val2.m12 + val1.m32 * val2.m22 + val1.m33 * val2.m32 + val1.m34 * val2.m42;
            result.m33 = val1.m31 * val2.m13 + val1.m32 * val2.m23 + val1.m33 * val2.m33 + val1.m34 * val2.m43;
            result.m34 = val1.m31 * val2.m14 + val1.m32 * val2.m24 + val1.m33 * val2.m34 + val1.m34 * val2.m44;

            result.m41 = val1.m41 * val2.m11 + val1.m42 * val2.m21 + val1.m43 * val2.m31 + val1.m44 * val2.m41;
            result.m42 = val1.m41 * val2.m12 + val1.m42 * val2.m22 + val1.m43 * val2.m32 + val1.m44 * val2.m42;
            result.m43 = val1.m41 * val2.m13 + val1.m42 * val2.m23 + val1.m43 * val2.m33 + val1.m44 * val2.m43;
            result.m44 = val1.m41 * val2.m14 + val1.m42 * val2.m24 + val1.m43 * val2.m34 + val1.m44 * val2.m44;

            return result;

        }
        public static Vector3D operator *(Matrix4D matrix, Vector3D vector)
        {
            Vector3D result = new Vector3D();

            float inverseW = 1.0f / (matrix.m41 + matrix.m42 + matrix.m43 + matrix.m44);

            result.x = ((matrix.m11 * vector.x) + (matrix.m12 * vector.y) + (matrix.m13 * vector.z) + matrix.m14) * inverseW;
            result.y = ((matrix.m21 * vector.x) + (matrix.m22 * vector.y) + (matrix.m23 * vector.z) + matrix.m24) * inverseW;
            result.z = ((matrix.m31 * vector.x) + (matrix.m32 * vector.y) + (matrix.m33 * vector.z) + matrix.m34) * inverseW;

            return result;
        }
        public static Matrix4D operator *(Matrix4D val1, float scalar)
        {
            Matrix4D result = new Matrix4D();

            result.m11 = val1.m11 * scalar;
            result.m12 = val1.m12 * scalar;
            result.m13 = val1.m13 * scalar;
            result.m14 = val1.m14 * scalar;

            result.m21 = val1.m21 * scalar;
            result.m22 = val1.m22 * scalar;
            result.m23 = val1.m23 * scalar;
            result.m24 = val1.m24 * scalar;

            result.m31 = val1.m31 * scalar;
            result.m32 = val1.m32 * scalar;
            result.m33 = val1.m33 * scalar;
            result.m34 = val1.m34 * scalar;

            result.m41 = val1.m41 * scalar;
            result.m42 = val1.m42 * scalar;
            result.m43 = val1.m43 * scalar;
            result.m44 = val1.m44 * scalar;

            return result;
        }
        public static Matrix4D operator +(Matrix4D val1, Matrix4D val2)
        {
            Matrix4D result = new Matrix4D();

            result.m11 = val1.m11 + val2.m11;
            result.m12 = val1.m12 + val2.m12;
            result.m13 = val1.m13 + val2.m13;
            result.m14 = val1.m14 + val2.m14;

            result.m21 = val1.m21 + val2.m21;
            result.m22 = val1.m22 + val2.m22;
            result.m23 = val1.m23 + val2.m23;
            result.m24 = val1.m24 + val2.m24;

            result.m31 = val1.m31 + val2.m31;
            result.m32 = val1.m32 + val2.m32;
            result.m33 = val1.m33 + val2.m33;
            result.m34 = val1.m34 + val2.m34;

            result.m41 = val1.m41 + val2.m41;
            result.m42 = val1.m42 + val2.m42;
            result.m43 = val1.m43 + val2.m43;
            result.m44 = val1.m44 + val2.m44;

            return result;
        }
        public static Matrix4D operator -(Matrix4D val1, Matrix4D val2)
        {
            Matrix4D result = new Matrix4D();

            result.m11 = val1.m11 - val2.m11;
            result.m12 = val1.m12 - val2.m12;
            result.m13 = val1.m13 - val2.m13;
            result.m14 = val1.m14 - val2.m14;

            result.m21 = val1.m21 - val2.m21;
            result.m22 = val1.m22 - val2.m22;
            result.m23 = val1.m23 - val2.m23;
            result.m24 = val1.m24 - val2.m24;

            result.m31 = val1.m31 - val2.m31;
            result.m32 = val1.m32 - val2.m32;
            result.m33 = val1.m33 - val2.m33;
            result.m34 = val1.m34 - val2.m34;

            result.m41 = val1.m41 - val2.m41;
            result.m42 = val1.m42 - val2.m42;
            result.m43 = val1.m43 - val2.m43;
            result.m44 = val1.m44 - val2.m44;

            return result;
        }
        public static Matrix4D operator -(Matrix4D val1)
        {
            Matrix4D result = new Matrix4D();
            result.m11 = -val1.m11;
            result.m12 = -val1.m12;
            result.m13 = -val1.m13;
            result.m14 = -val1.m14;

            result.m21 = -val1.m21;
            result.m22 = -val1.m22;
            result.m23 = -val1.m23;
            result.m24 = -val1.m24;

            result.m31 = -val1.m31;
            result.m32 = -val1.m32;
            result.m33 = -val1.m33;
            result.m34 = -val1.m34;

            result.m41 = -val1.m41;
            result.m42 = -val1.m42;
            result.m43 = -val1.m43;
            result.m44 = -val1.m44;

            return result;
        }
        public static bool operator ==(Matrix4D val1, Matrix4D val2)
        {
            if (
                    val1.m11 == val2.m11 && val1.m12 == val2.m12 && val1.m13 == val2.m13 && val1.m14 == val2.m14 &&
                    val1.m21 == val2.m21 && val1.m22 == val2.m22 && val1.m23 == val2.m23 && val1.m24 == val2.m24 &&
                    val1.m31 == val2.m31 && val1.m32 == val2.m32 && val1.m33 == val2.m33 && val1.m34 == val2.m34 &&
                    val1.m41 == val2.m41 && val1.m42 == val2.m42 && val1.m43 == val2.m43 && val1.m44 == val2.m44)
                return true;

            return false;
        }
        public static bool operator !=(Matrix4D val1, Matrix4D val2)
        {
            return !(val1 == val2);
        }
        public float Determinant
        {
            get
            {
                float result = m11 * (m22 * (m33 * m44 - m43 * m34) - m23 * (m32 * m44 - m42 * m34) + m24 * (m32 * m43 - m42 * m33)) -
                               m12 * (m21 * (m33 * m44 - m43 * m34) - m23 * (m31 * m44 - m41 * m34) + m24 * (m31 * m43 - m41 * m33)) +
                               m13 * (m21 * (m32 * m44 - m42 * m34) - m22 * (m31 * m44 - m41 * m34) + m24 * (m31 * m42 - m41 * m32)) -
                               m14 * (m21 * (m32 * m43 - m42 * m33) - m22 * (m31 * m43 - m41 * m33) + m23 * (m31 * m42 - m41 * m32));

                return result;
            }
        }
        private Matrix4D Adjoint()
        {
            
            float val0 =    m22 * (m33 * m44 - m43 * m34) - m23 * (m32 * m44 - m42 * m34) + m24 * (m32 * m43 - m42 * m33);
            float val1 =  -(m12 * (m33 * m44 - m43 * m34) - m13 * (m32 * m44 - m42 * m34) + m14 * (m32 * m43 - m42 * m33));
            float val2 =    m12 * (m23 * m44 - m43 * m24) - m13 * (m22 * m44 - m42 * m24) + m14 * (m22 * m43 - m42 * m23);
            float val3 =  -(m12 * (m23 * m34 - m33 * m24) - m13 * (m22 * m34 - m32 * m24) + m14 * (m22 * m33 - m32 * m23));
            float val4 =  -(m21 * (m33 * m44 - m43 * m34) - m23 * (m31 * m44 - m41 * m34) + m24 * (m31 * m43 - m41 * m33));
            float val5 =    m11 * (m33 * m44 - m43 * m34) - m13 * (m31 * m44 - m41 * m34) + m14 * (m31 * m43 - m41 * m33);
            float val6 =  -(m11 * (m23 * m44 - m43 * m24) - m13 * (m21 * m44 - m41 * m24) + m14 * (m21 * m43 - m41 * m23));
            float val7 =    m11 * (m23 * m34 - m33 * m24) - m13 * (m21 * m34 - m31 * m24) + m14 * (m21 * m33 - m31 * m23);
            float val8 =    m21 * (m32 * m44 - m42 * m34) - m22 * (m31 * m44 - m41 * m34) + m24 * (m31 * m42 - m41 * m32);
            float val9 =  -(m11 * (m32 * m44 - m42 * m34) - m12 * (m31 * m44 - m41 * m34) + m14 * (m31 * m42 - m41 * m32));
            float val10 =   m11 * (m22 * m44 - m42 * m24) - m12 * (m21 * m44 - m41 * m24) + m14 * (m21 * m42 - m41 * m22);
            float val11 = -(m11 * (m22 * m34 - m32 * m24) - m12 * (m21 * m34 - m31 * m24) + m14 * (m21 * m32 - m31 * m22));
            float val12 = -(m21 * (m32 * m43 - m42 * m33) - m22 * (m31 * m43 - m41 * m33) + m23 * (m31 * m42 - m41 * m32));
            float val13 =   m11 * (m32 * m43 - m42 * m33) - m12 * (m31 * m43 - m41 * m33) + m13 * (m31 * m42 - m41 * m32);
            float val14 = -(m11 * (m22 * m43 - m42 * m23) - m12 * (m21 * m43 - m41 * m23) + m13 * (m21 * m42 - m41 * m22));
            float val15 =   m11 * (m22 * m33 - m32 * m23) - m12 * (m21 * m33 - m31 * m23) + m13 * (m21 * m32 - m31 * m22);

            return new Matrix4D(val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, val10, val11, val12, val13, val14, val15);
        }

        public Matrix4D Transpose()
        {
            return new Matrix4D(this.m11, this.m21, this.m31, this.m41,
                              this.m12, this.m22, this.m32, this.m42,
                              this.m13, this.m23, this.m33, this.m43,
                              this.m14, this.m24, this.m34, this.m44);
        }
        public Matrix4D Inverse()
        {
            return Adjoint() * (1.0f / this.Determinant);
        }

        public override bool Equals(object obj)
        {
            if (obj is Matrix4D)
                return (this == (Matrix4D)obj);
            else
                return false;
        }

        public override int GetHashCode()
        {
            return (int)(m11 + m12 + m13 + m14 + m21 + m22 + m23 + m24 + m31 + m32 + m33 + m34 + m41 + m42 + m43 + m44);
        }
        /*public static Matrix CreateRotationX(float rotation)
        {

        }*/
    }
}
